const TextModes = {
    DATA: "DATA",
    RCDATA: "RCDATA",
    RAWTEXT: "RAWTEXT",
    CDATA: "CDATA",
};

function isEnd(context, ancestors) {
    console.log("isEnd", context, "ancestors", ancestors)
        //当模板内容解析完毕后，停止
    if (!context.source) {
        return true;
    }
    //获取父级标签
    const parent = ancestors[ancestors.length - 1];
    //如果遇到结束标签，且该标签与父级标签同名，就停止
    if (parent && context.source.startsWith(`</${parent.tag}>`)) {
        return true;
    }
    return false;
}
//解析插值
function parseInterpolation(context) {
    //消费节点符号
    context.advanceBy("{{".length);
    //找到结束界定符的位置索引
    const closeIndex = context.source.indexOf("}}");
    if (closeIndex < 0) {
        console.error("插值缺少结束定界符");
    }
    //截取界定符月结结束界定符之间的内容作为插值的表达式
    const content = context.source.slice(0, closeIndex);
    //消费表达式
    context.advanceBy(content.length);
    //消费结束界定符
    context.advanceBy("}}".length);
    return {
        type: "Interpolation",
        content: {
            type: "Expression",
            content, //暂时不用html解码表达式
        }
    }
}
//解析属性
function parseAttributes(context) {
    const { advanceBy, advanceSpaces } = context;
    //用来存储解析过程中尝试的属性节点和指令节点

    const props = [];
    //开启while循环，不断的消费模板内容，知道遇到【结束部分】位置
    while (!context.source.startsWith(">") && !context.source.startsWith("/>")) {
        //解析指令
        //该正则用于匹配属性名称
        const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source);
        //得到属性名称
        const name = match[0];
        //是不是指令属性
        const isDirective = name.startsWith("v-");
        //消费属性名称
        advanceBy(name.length);
        //消费掉等号
        advanceBy(1);
        //消费属性名称之间的空白字符
        advanceSpaces();

        //属性值
        let value = "";
        //获取当前目标内容的第一个字符
        const quote = context.source[0];
        //判断属性值是否被引号引用
        const isQuoted = quote === '"' || quote === "'";
        if (isQuoted) {
            //属性呗引号引用，消费引号
            advanceBy(1);
            //获取下一个引号的索引
            const endQuoteIndex = context.source.indexOf(quote);
            if (endQuoteIndex) {
                //获取下一个引号前的内容作为属性
                value = context.source.slice(0, endQuoteIndex);
                //消费属性值
                advanceBy(value.length);
                //消费引号
                advanceBy(1);
            } else {
                console.error("缺少引号");
            }
        } else {
            /**
             * 代码运行到这里说明属性值没有被括号引用
             * 下一个空白芝罘区的内容全部作为属性值
             */
            const match = /^[^\t\r\n\f >]+/.exec(context.source);
            //获取属性值
            value = match[0];
            //消费属性值
            advanceBy(value.length);
        }
        //消费属性后面的空白符号
        advanceSpaces();
        console.log("context.source", name, value);
        //使用属性+属性值创建一个节点，添加到props数组中
        props.push({
            type: isDirective ? "Directive" : "Attribute",
            name,
            value,
        });
    }
    console.log("props", props);
    //返回结果
    return props;
}
//解析文本
function parseText(context) {
    const { advanceBy } = context;
    //endIndex为文本内容的结尾索引，默认将整个模板的剩余内容座位文本内人
    let endIndex = context.source.length;
    //寻找字符串<索引
    const ltIndex = context.source.indexOf("<");
    //寻找定界符{{的位置索引
    const delimiterIndex = context.source.indexOf("{{");
    //取ltIndex和当前endIndex中较小的一个新的【结尾索引】
    if (ltIndex > -1 && ltIndex < endIndex) {
        endIndex = ltIndex;
    }
    //去delimiterIndex和当前endIndex中较小的一个新的结尾索引
    if (delimiterIndex > -1 && delimiterIndex < endIndex) {
        endIndex = delimiterIndex;
    }
    //此时endIndex就是最终文本的结尾索引，调用slice截取文本内容
    const content = context.source.slice(0, endIndex);
    //消耗掉文本内容
    advanceBy(content.length);
    //返回文本节点
    return {
        type: "Text",
        content,
    };
}

/**
 * 由于parseTag即用来处理开始标签，也用来处理接触标签
 * 因此设计了第二个参数type
 * 用来处理当前处理的是开始标签还是结束标签
 * ，type默认是start，即默认是开始标签处理
 * @param {*} context
 * @param {*} type
 */
function parseTag(context, type = "start") {
    const { advanceBy, advanceSpaces } = context;
    //处理开始标签和技术标签的正则表达式不同
    const match =
        type === "start" ?
        /^<([a-z][^\t\r\n\f />]*)/i.exec(context.source) :
        /^<\/([a-z][^\t\r\n\f />]*)/i.exec(context.source);
    //匹配成功后，正则表达式的第一个捕获组的值就是标签的名称
    const tag = match[1];
    //消费正则表达式匹配的全部内容，例如 '<div' 这段内容
    advanceBy(match[0].length);
    //消费标签中的无用空白字符串
    advanceSpaces();
    /**
     * 调用parseAttribute函数完成属性与指令的解析，并得到props数组
     * props数组是由指令节点和属性节点共同完成的数组
     */
    const props = parseAttributes(context);
    console.log("props props", props);
    //消费匹配的内容后，如果字符串以"/>"开头，则说明这是一个闭合的标签
    const isSelfClosing = context.source.startsWith("/>");
    //如果是自闭合标签，则消费"/>"，否则消费">"
    advanceBy(isSelfClosing ? 2 : 1);
    //返回标签节点
    return {
        type: "Element",
        tag,
        //子节点留空
        children: [],
        //是否是闭合
        isSelfClosing,
        props, //添加属性信息
    };
}
//解析标签元素
function parseElement(context, ancestors) {
    //根据标签的开始标签，得到匹配到的标签的信息，例如标签名字，以及是否是自闭标签
    const element = parseTag(context);
    //自闭函数
    if (element.isSelfClosing) {
        return element;
    }
    ///切换到正确的文本模式=====暂时用不到----start
    if (element.tag === "textarea" || element.tag === "title") {
        context.mode = TextModes.RCDATA;
    } else if (/style|xmp|iframe|noembed|noframes|noscript/.test(element.tag)) {
        context.mode = TextModes.RAWTEXT;
    } else {
        context.mode = TextModes.DATA;
    }
    ///切换到正确的文本模式=====暂时用不到----end
    console.log("parseElement，context.source", context.source);
    ancestors.push(element);
    //ancestors充当父组件，
    element.children = parseChildren(context, ancestors);
    //使用完后，移除
    ancestors.pop();
    if (context.source.startsWith(`</${element.tag}>`)) {
        parseTag(context, "end");
    } else {
        console.error(`${element.tag}标签缺少闭合标签`);
    }
    return element;
}
/**
 * 接受上下文，和父组件
 * @param {*} context
 * @param {*} ancestors
 */
function parseChildren(context, ancestors) {
    //定义nodes数组存储子节点，它作为最终的返回值
    let nodes = [];
    const { mode, source } = context;
    console.log("mode", mode, "source", source, source[1]);
    let index = 0;
    /**
     * 开启while循环，只要开启条件，只要满足条件就会一直进行解析
     */
    while (!isEnd(context, ancestors) && index < 200) {
        index++;
        let node;
        //只有DATA模式和RCDATA模式才支撑插值节点的解析
        if (mode === TextModes.DATA || mode === TextModes.RCDATA) {

            //只有DATA模式才支撑标签节点的解析
            if (mode === TextModes.DATA && context.source[0] === "<") {
                if (source[1] === "!") {
                    if (source.startsWith("<!--")) {
                        //注释
                        node = parseComment(context);
                    } else if (source.startsWith("<![CDATA[")) {
                        node = parseCDATA(context, ancestors);
                    }
                } else if (source[1] === "/") {
                    //结束标签，这里需要抛出错误，后文会详细解释原因
                    console.error("无效的结束标签", source);
                    break;
                    //continue;
                } else if (/[a-z]/i.test(source[1])) {
                    //标签
                    node = parseElement(context, ancestors);
                }
            } else if (context.source.startsWith("{{")) {
                //解析插值
                node = parseInterpolation(context);
            }
        }

        //node不存在，说明处于其他模式，即非DATA模式且非RCDATA模式
        if (!node) {
            node = parseText(context);
        }

        //将节点添加到nodes数组中
        nodes.push(node);
    }
    //放回子节点
    return nodes;
}
//解析器函数，接受模板作为参数
function parse(str) {
    //定义上下文对象
    const context = {
        ///source是目标内容，用于解析过程中的消费
        source: str,
        //解析器当前处于文本模式，初始化为DATA
        mode: TextModes.DATA,
        advanceBy(num) {
            //根据给定的字符串num，截取为止num后的目标内容，并替换当前的目标内容
            context.source = context.source.slice(num);
        },
        //无论开始标签还是结束标签，都可能存在无用空白字符，例如"<div  >"
        advanceSpaces() {
            //匹配空白字符,正则后面是由空格的
            const match = /^[\t\r\n\f ]+/.exec(context.source);
            console.log("match", match);
            if (match) {
                //调用advanceBy函数消费空白字符串
                context.advanceBy(match[0].length);
            }
        },
    };
    /**
     * 低啊用parseChildren含税进行解析，它放回解析后得到的对象
     * parseChildren
     *  函数接受两个参数
     *  第一个参数是上下文对象
     * 第二个参数是由父带节点构成的节点栈，初始化节点栈为空
     *
     */
    const nodes = parseChildren(context, []);
    //返回Root根节点
    return {
        type: "Root",
        //使用nodes作为根节点的children
        children: nodes,
    };
}